package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.album.TrackStatMqVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {


    @Autowired
    private AlbumFeignClient albumFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    /**
     * 将指定专辑ID，封装为索引库文档对象，存入索引库
     *
     * @param albumId
     */
    @Override
    public void upperAlbum(Long albumId) {
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        //1.封装专辑索引库文档对象专辑相关信息
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            //1.1 远程调用"专辑服务"根据专辑ID查询专辑基本信息
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            Assert.notNull(albumInfo, "专辑：{},信息不存在", albumId);
            //1.2 将专辑基本信息拷贝创建专辑索引库文档对象
            BeanUtil.copyProperties(albumInfo, albumInfoIndex);

            //1.3 封装专辑标签列表
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
            if (CollectionUtil.isNotEmpty(albumAttributeValueVoList)) {
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList
                        .stream()
                        .map(a -> BeanUtil.copyProperties(a, AttributeValueIndex.class))
                        .collect(Collectors.toList());
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            }
            return albumInfo;
        }, threadPoolExecutor);


        //2.封装专辑索引库文档对象分类信息
        CompletableFuture<Void> categoryCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //2.1 远程调用"专辑服务"根据三级分类ID查询分类视图
            BaseCategoryView categoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(categoryView, "专辑：{}，分类为空", albumId);
            //2.2 封装1,2级分类ID
            albumInfoIndex.setCategory1Id(categoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(categoryView.getCategory2Id());
        }, threadPoolExecutor);


        //3.封装专辑索引库文档对象统计信息（热度）（随机产生） TODO:后续改为动态查询专辑服务获取专辑统计信息
        //3.1 随机生成统计数值
        int num1 = RandomUtil.randomInt(0, 3000);
        int num2 = RandomUtil.randomInt(0, 2000);
        int num3 = RandomUtil.randomInt(0, 1000);
        int num4 = RandomUtil.randomInt(0, 500);
        albumInfoIndex.setPlayStatNum(num1);
        albumInfoIndex.setSubscribeStatNum(num2);
        albumInfoIndex.setBuyStatNum(num3);
        albumInfoIndex.setCommentStatNum(num4);

        //3.2 基于随机统计值计算热度 不同行为设置不同权重
        BigDecimal bigDecimal1 = new BigDecimal(num1).multiply(new BigDecimal("0.1"));
        BigDecimal bigDecimal2 = new BigDecimal(num2).multiply(new BigDecimal("0.2"));
        BigDecimal bigDecimal3 = new BigDecimal(num3).multiply(new BigDecimal("0.3"));
        BigDecimal bigDecimal4 = new BigDecimal(num4).multiply(new BigDecimal("0.4"));
        double hotScore = bigDecimal1.add(bigDecimal2).add(bigDecimal3).add(bigDecimal4).doubleValue();
        albumInfoIndex.setHotScore(hotScore);

        //4.封装专辑索引库文档对象主播信息
        CompletableFuture<Void> userInfoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            //4.1 远程调用"用户服务"根据用户ID查询用户基本信息
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData();
            Assert.notNull(userInfoVo, "专辑：{}，作者：{}不存在", albumId, albumInfo.getUserId());
            //4.2 封装主播名称
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolExecutor);


        //5.组合所有异步任务，要求必须全部执行完毕
        CompletableFuture.allOf(
                albumInfoCompletableFuture,
                categoryCompletableFuture,
                userInfoCompletableFuture
        ).join();


        //6.保存专辑索引库文档到索引库
        albumInfoIndexRepository.save(albumInfoIndex);

        //7.将发布专辑名称存入提词索引库中
        this.saveSuggestDoc(albumInfoIndex);

        //8.将上架专辑ID存入布隆过滤器
        RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
        bloomFilter.add(albumInfoIndex.getId());
    }

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 将专辑标题构建提词索引库文档对象，存入索引库
     *
     * @param albumInfoIndex
     */
    @Override
    public void saveSuggestDoc(AlbumInfoIndex albumInfoIndex) {
        //1.创建提词索引库文档对象
        SuggestIndex suggestIndex = new SuggestIndex();
        //2.给提词文档中属性赋值
        suggestIndex.setId(albumInfoIndex.getId().toString());
        //2.1 原始内容 用于给用户展示提示词选型
        String albumTitle = albumInfoIndex.getAlbumTitle();
        suggestIndex.setTitle(albumTitle);
        //2.2 建议词字段 汉字
        suggestIndex.setKeyword(new Completion(new String[]{albumTitle}));
        //2.3 建议词字段 汉语拼音
        String pinyin = PinyinUtil.getPinyin(albumTitle, "");
        suggestIndex.setKeywordPinyin(new Completion(new String[]{pinyin}));
        //2.4 建议词字段 汉语拼音首字母
        String firstLetter = PinyinUtil.getFirstLetter(albumTitle, "");
        suggestIndex.setKeywordSequence(new Completion(new String[]{firstLetter}));
        //3.保存提词文档
        suggestIndexRepository.save(suggestIndex);
    }

    /**
     * 根据用户已录入文本进行展示相关提示词列表，实现自动补全
     * 需求：提示下拉框联想词最多展示10个,如果自动补全结果不足10个，尝试采用全文查询进行补全
     *
     * @param keyword
     * @return
     */
    @Override
    public List<String> completeSuggest(String keyword) {
        try {
            //1.发起自动补全请求-设置建议请求参数
            SearchResponse<SuggestIndex> searchResponse = elasticsearchClient
                    .search(
                            s -> s.index(SUGGEST_INDEX_NAME)
                                    .suggest(
                                            s1 -> s1.suggesters("letter-suggest", sf -> sf.prefix(keyword).completion(c -> c.field("keywordSequence").size(10).skipDuplicates(true)))
                                                    .suggesters("pinyin-suggest", sf -> sf.prefix(keyword).completion(c -> c.field("keywordPinyin").size(10).skipDuplicates(true).fuzzy(f -> f.fuzziness("1"))))
                                                    .suggesters("keyword-suggest", sf -> sf.prefix(keyword).completion(c -> c.field("keyword").size(10).skipDuplicates(true)))
                                    ),
                            SuggestIndex.class
                    );
            //2.解析ES响应建议词结果
            Map<String, List<Suggestion<SuggestIndex>>> suggestMap = searchResponse.suggest();
            //2.1 获取建议结果对象，分别从不同建议词结果中获取提示词列表
            Set<String> suggestResultSet = new HashSet<>();
            //2.2 将解析三个建议词结果提示词存入集合中（去重）
            suggestResultSet.addAll(this.parseSuggestResult("letter-suggest", suggestMap));
            suggestResultSet.addAll(this.parseSuggestResult("pinyin-suggest", suggestMap));
            suggestResultSet.addAll(this.parseSuggestResult("keyword-suggest", suggestMap));
            //2.3 如果得到建议词结果长度小于10采用全文检索补全
            if (suggestResultSet.size() < 10) {
                //2.3.1 全文检索专辑索引库
                SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(
                        s -> s.index(INDEX_NAME)
                                .query(q -> q.match(m -> m.query(keyword).field("albumTitle")))
                                .size(10),
                        AlbumInfoIndex.class
                );
                //2.3.2 解析命中索引库中专辑标题
                List<Hit<AlbumInfoIndex>> hits = response.hits().hits();
                if (CollectionUtil.isNotEmpty(hits)) {
                    for (Hit<AlbumInfoIndex> hit : hits) {
                        AlbumInfoIndex albumInfoIndex = hit.source();
                        String albumTitle = albumInfoIndex.getAlbumTitle();
                        suggestResultSet.add(albumTitle);
                        if (suggestResultSet.size() >= 10) {
                            break;
                        }
                    }
                }
            }
            //2.4 最终提示词列表最多是10个
            List<String> list = new ArrayList<>(suggestResultSet);
            if (list.size() < 10) {
                return list;
            } else {
                return list.subList(0, 10);
            }
        } catch (IOException e) {
            log.error("[搜索服务]，关键字自动补全异常：", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 解析不用建议词参数对应提词结果
     *
     * @param suggestName 建议词参数名称
     * @param suggestMap  ES响应结果
     * @return
     */
    @Override
    public Collection<String> parseSuggestResult(String suggestName, Map<String, List<Suggestion<SuggestIndex>>> suggestMap) {
        List<String> list = new ArrayList<>();
        List<Suggestion<SuggestIndex>> suggestionList = suggestMap.get(suggestName);
        if (CollectionUtil.isNotEmpty(suggestionList)) {
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : suggestionList) {
                //获取建议结果中options数组
                List<CompletionSuggestOption<SuggestIndex>> options = suggestIndexSuggestion.completion().options();
                if (CollectionUtil.isNotEmpty(options)) {
                    for (CompletionSuggestOption<SuggestIndex> option : options) {
                        SuggestIndex suggestIndex = option.source();
                        //将提词文档中原始中文返回
                        String title = suggestIndex.getTitle();
                        list.add(title);
                    }
                }
            }
        }
        return list;
    }

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 更新ES索引库文档统计信息
     *
     * @param trackStatMqVo
     */
    @Override
    public void updateAlbumStat(TrackStatMqVo trackStatMqVo) {
        //1.避免同一个统计消息被重复消息，利用Redis命令set k v [ex][nx]某次统计消息只有第一次能存入成功
        //1.1 创建key
        String key = RedisConstant.BUSINESS_PREFIX + "search:" + trackStatMqVo.getBusinessNo();
        //1.2 尝试存入redis
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "", 10, TimeUnit.MINUTES);
        if (flag) {
            Optional<AlbumInfoIndex> optional = albumInfoIndexRepository.findById(trackStatMqVo.getAlbumId());
            if (optional.isPresent()) {
                AlbumInfoIndex albumInfoIndex = optional.get();
                if (SystemConstant.TRACK_STAT_PLAY.equals(trackStatMqVo.getStatType())) {
                    albumInfoIndex.setPlayStatNum(albumInfoIndex.getPlayStatNum() + trackStatMqVo.getCount());
                }
                if (SystemConstant.TRACK_STAT_COMMENT.equals(trackStatMqVo.getStatType())) {
                    albumInfoIndex.setCommentStatNum(albumInfoIndex.getCommentStatNum() + trackStatMqVo.getCount());
                }
                albumInfoIndexRepository.save(albumInfoIndex);
            }
        }
    }

    /**
     * 更新Redis缓冲中不同分类下不同排序方式TOP10列表 作业：改为多线程版本
     */
    @Override
    public void updateLatelyAlbumRanking() {
        try {
            //1.远程调用专辑服务获取所有1级分类ID
            List<BaseCategory1> baseCategory1List = albumFeignClient.getAllCategory1().getData();
            if (CollectionUtil.isNotEmpty(baseCategory1List)) {
                List<Long> baseCategory1IdList = baseCategory1List.
                        stream()
                        .map(BaseCategory1::getId)
                        .collect(Collectors.toList());
                //2.遍历1级分类列表
                String[] rankingDimensionArray = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
                for (Long baseCategoryId : baseCategory1IdList) {
                    //3.遍历不同排序方式：每个分类下五种排序字段TOP10列表，放入Redis
                    for (String dimension : rankingDimensionArray) {
                        //3.1 查询指定1级下某个排序方式TOP10列表
                        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient
                                .search(
                                        s -> s.index(INDEX_NAME)
                                                .query(q -> q.term(t -> t.field("category1Id").value(baseCategoryId)))
                                                .size(10)
                                                .sort(sort -> sort.field(f -> f.field(dimension).order(SortOrder.Desc)))
                                                .source(source -> source.filter(f -> f.excludes(Arrays.asList("isFinished", "category1Id", "category2Id", "category3Id", "hotScore", "attributeValueIndexList.attributeId", "attributeValueIndexList.valueId")))),
                                        AlbumInfoIndex.class
                                );
                        //3.2 解析ES响应结果
                        List<Hit<AlbumInfoIndex>> hitList = searchResponse.hits().hits();
                        if (CollectionUtil.isNotEmpty(hitList)) {
                            List<AlbumInfoIndex> top10List = hitList.stream()
                                    .map(hit -> hit.source())
                                    .collect(Collectors.toList());

                            //3.3 将查询到列表数据放入到Redis中Hash
                            //3.3.1 构建排行榜Hash的KEY
                            String key = RedisConstant.RANKING_KEY_PREFIX + baseCategoryId;
                            //3.3.2 构建排行榜Hash的field(hash key)
                            String field = dimension;
                            //3.3.3 将TOP10列表放入Hash中Value
                            redisTemplate.opsForHash().put(key, field, top10List);
                        }
                    }
                }
            }
        } catch (IOException e) {
            log.error("[搜索服务]查询ES更新Redis中TOP10排行榜异常：", e);
            throw new RuntimeException(e);
        }

    }

    /**
     * 获取某个下某种排序方式TOP10排行榜
     *
     * @param category1Id
     * @param dimension
     * @return
     */
    @Override
    public List<AlbumInfoIndex> getRankingList(Long category1Id, String dimension) {
        //3.3.1 构建排行榜Hash的KEY
        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        //3.3.2 构建排行榜Hash的field(hash key)
        String field = dimension;
        return (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(key, field);
    }


    /**
     * 将指定专辑从索引库删除
     *
     * @param albumId
     */
    @Override
    public void lowerAlbum(Long albumId) {
        //删除文档索引库中文档
        albumInfoIndexRepository.deleteById(albumId);
        //删除提词索引库中文档
        suggestIndexRepository.deleteById(albumId.toString());
    }


    @Autowired
    private ElasticsearchClient elasticsearchClient;

    /**
     * 站内检索专辑
     *
     * @param albumIndexQuery 查询条件
     * @return
     */
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        try {
            //1.基于入参中条件对象封装检索请求对象SearchReqeust
            SearchRequest searchRequest = this.buildDSL(albumIndexQuery);
            //System.err.println("本次检索DSL：");
            //System.err.println(searchRequest);
            //2.调用ES执行检索
            SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);

            //3.解析ES响应结果封装自定义结果对象AlbumSearchResponseVo
            return this.parseResult(searchResponse, albumIndexQuery);
        } catch (Exception e) {
            log.error("[搜索服务]站内搜索异常：", e);
            throw new RuntimeException(e);
        }
    }

    private static final String INDEX_NAME = "albuminfo";
    private static final String SUGGEST_INDEX_NAME = "suggestinfo";

    /**
     * 基于提交请求参数封装检索ES所需检索请求对象
     *
     * @param albumIndexQuery 条件
     * @return 检索请求对象
     */
    @Override
    public SearchRequest buildDSL(AlbumIndexQuery albumIndexQuery) {
        //1.创建检索请求构建器Builder对象
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(INDEX_NAME);
        //2. 在Builder对象中封装相关请求路径参数 "query","from","size","sort","highlight","_source“
        //2.1 设置查询条件 请求体参数"query"部分参数 创建最大条件对象
        BoolQuery.Builder allBoolQueryBuilder = new BoolQuery.Builder();
        String keyword = albumIndexQuery.getKeyword();
        if (StringUtils.isNotBlank(keyword)) {
            //2.1.1 设置大条件一：关键字 must  嵌套一个bool 三个子条件：should 匹配查询标题/简介;精确查询主播名称
            allBoolQueryBuilder.must(m -> m.bool(
                    b -> b.should(s -> s.match(m1 -> m1.field("albumTitle").query(keyword)))
                            .should(s -> s.match(m2 -> m2.field("albumIntro").query(keyword)))
                            .should(s -> s.term(t -> t.field("announcerName").value(keyword)))
            ));
        }
        //2.1.2 设置大条件二：分类   filter
        //2.1.2.1 设置1级分类
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null) {
            allBoolQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(category1Id)));
        }
        //2.1.2.2 设置2级分类
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null) {
            allBoolQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(category2Id)));
        }
        //2.1.2.3 设置3级分类
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null) {
            allBoolQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(category3Id)));
        }
        //2.1.3 设置大条件三：标签   filter 前端提交形式：List<String> 字符串=标签id:标签值id
        //关键点：每个标签条件构建对应Nested查询对象放入filter过滤
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (CollectionUtil.isNotEmpty(attributeList)) {
            //2.1.3.1 遍历标签条件，每遍历一次设置Nested查询
            for (String s : attributeList) {
                String[] split = s.split(":");
                if (split != null && split.length == 2) {
                    // 动态在Nested查询中设置标签ID，标签值的ID
                    allBoolQueryBuilder.filter(f -> f.nested(
                            n -> n.path("attributeValueIndexList").query(q -> q.bool(
                                    b -> b.must(m -> m.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                                            .must(m -> m.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))
                            ))
                    ));
                }
            }
        }
        builder.query(allBoolQueryBuilder.build()._toQuery());
        //2.2 设置分页 请求体参数"from"-起始位置,"size"
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        int from = (pageNo - 1) * pageSize;
        builder.from(from).size(pageSize);
        //2.3 满足条件才设置排序 请求体参数中参数"sort" 前后端阅读参数格式：综合排序[1:desc] 播放量[2:desc] 发布时间[3:desc]
        String order = albumIndexQuery.getOrder();
        if (StringUtils.isNotBlank(order)) {
            //2.3.1 先根据冒号进行分隔
            String[] split = order.split(":");
            if (split != null && split.length == 2) {
                //2.3.2 确定排序字段
                String orderFiled = "";
                switch (split[0]) {
                    case "1":
                        orderFiled = "hotScore";
                        break;
                    case "2":
                        orderFiled = "playStatNum";
                        break;
                    case "3":
                        orderFiled = "createTime";
                        break;
                }
                //2.3.3 确定排序方向
                SortOrder sortOrder = "asc".equals(split[1]) ? SortOrder.Asc : SortOrder.Desc;
                String finalOrderFiled = orderFiled;
                //2.3.4 设置动态排序
                builder.sort(s -> s.field(f -> f.field(finalOrderFiled).order(sortOrder)));
            }
        }
        //2.4 设置高亮 请求体参数中"highlight"
        if (StringUtils.isNotBlank(albumIndexQuery.getKeyword())) {
            builder.highlight(h -> h.fields("albumTitle", f -> f.preTags("<font style='color:red'>").postTags("</font>")));
        }
        //2.5 设置字段过滤 请求体参数中"_source"
        builder.source(s -> s.filter(f -> f.excludes(Arrays.asList("isFinished", "category1Id", "category2Id", "category3Id", "hotScore", "attributeValueIndexList.attributeId", "attributeValueIndexList.valueId"))));

        //3.基于builder对象构建实际检索请求对象
        return builder.build();
    }

    /**
     * 对ES检索返回结果进行解析，封装AlbumSearchResponseVo响应客户端
     *
     * @param searchResponse  ES响应结果对象
     * @param albumIndexQuery 检索条件对象
     * @return
     */
    @Override
    public AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery albumIndexQuery) {
        //1.创建响应结果VO对象
        AlbumSearchResponseVo vo = new AlbumSearchResponseVo();
        //2.封装分页信息
        //2.1 解析ES结果获取总记录数
        HitsMetadata<AlbumInfoIndex> hits = searchResponse.hits();
        long total = hits.total().value();
        //2.2 获取入参条件中页大小
        Integer pageSize = albumIndexQuery.getPageSize();
        //2.3 计算总页数
        long totalPages = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        //2.4 封装分页参数
        vo.setTotal(total);
        vo.setTotalPages(totalPages);
        vo.setPageNo(albumIndexQuery.getPageNo());
        vo.setPageSize(pageSize);
        //3.封装检索到业务数据列表
        List<Hit<AlbumInfoIndex>> hitsList = hits.hits();
        if (CollectionUtil.isNotEmpty(hitsList)) {
            List<AlbumInfoIndexVo> list = hitsList.stream()
                    .map(hit -> {
                        //3.1 将检索得到Hit对象转为要求AlbumInfoIndexVo对象
                        AlbumInfoIndexVo albumInfoIndexVo = BeanUtil.copyProperties(hit.source(), AlbumInfoIndexVo.class);
                        //3.2 判断Hit对象是否存在高亮文本片段
                        Map<String, List<String>> highlight = hit.highlight();
                        if (CollectionUtil.isNotEmpty(highlight)) {
                            //3.3 获取高亮文本片段
                            String albumTitleHighlight = highlight.get("albumTitle").get(0);
                            albumInfoIndexVo.setAlbumTitle(albumTitleHighlight);
                        }
                        return albumInfoIndexVo;
                    }).collect(Collectors.toList());
            vo.setList(list);
        }
        //4.响应封装完毕VO对象
        return vo;
    }


    /**
     * 查询1级下置顶三级分类热门前6专辑
     *
     * @param category1Id
     * @return
     */
    @Override
    public List<Map<String, Object>> getTop6AlbumByCategory1(Long category1Id) {
        try {
            //1.远程调用"专辑服务"获取1级分类下置顶7个三级分类列表
            List<BaseCategory3> baseCategory3List = albumFeignClient.getTop7Category3(category1Id).getData();
            Assert.notNull(baseCategory3List, "该分类{}下无三级分类", category1Id);
            //1.1 遍历得到所有三级分类ID集合
            List<Long> baseCategory3IdList = baseCategory3List.stream().map(BaseCategory3::getId).collect(Collectors.toList());
            //1.2 执行多关键字检索需要List<FieldValue> 故 转换类型
            List<FieldValue> fieldValueList = baseCategory3IdList.stream()
                    .map(category3Id -> FieldValue.of(category3Id)).collect(Collectors.toList());

            //1.3 后续解析结果方便获取三级分类对象 将三级集合转为Map Key:三级分类ID Value：三级分类对象
            Map<Long, BaseCategory3> baseCategory3Map = baseCategory3List
                    .stream()
                    .collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));

            //2.执行ES检索
            SearchResponse<AlbumInfoIndex> searchResponse =
                    elasticsearchClient.search(
                            s -> s.index(INDEX_NAME)
                                    .query(q -> q.terms(t -> t.field("category3Id").terms(t1 -> t1.value(fieldValueList))))
                                    .size(0)
                                    .aggregations(
                                            "category3_agg", a -> a.terms(t -> t.field("category3Id").size(20))
                                                    .aggregations("top6", a1 -> a1.topHits(t -> t.size(6).sort(sort -> sort.field(f -> f.field("hotScore").order(SortOrder.Desc)))))
                                    ),
                            AlbumInfoIndex.class
                    );
            //3.解析ES响应结果，封装自定义结果List<Map<String, Object>>代表所有置顶三级分类热门专辑列表
            //3.1 获取ES响应中聚合结果对象，根据请求体参数聚合名称，获取三级分类聚合对象
            Aggregate category3_agg = searchResponse.aggregations().get("category3_agg");
            LongTermsAggregate category3Lterms = category3_agg.lterms();
            //3.2 获取三级分类聚合结果Buckets桶数组 遍历Bucket数组 每遍历一个Bucket处理一个置顶三级分类 Map
            List<LongTermsBucket> category3Buckets = category3Lterms.buckets().array();
            List<Map<String, Object>> list = category3Buckets.stream().map(category3Bucket -> {
                //3.3 获取三级分类ID
                long topCategory3Id = category3Bucket.key();
                //3.4 遍历当前三级分类聚合中子聚合得到热门专辑前6个列表
                Aggregate top6 = category3Bucket.aggregations().get("top6");
                List<AlbumInfoIndex> hotAlbumIndexList = top6.topHits().hits().hits()
                        .stream()
                        .map(hit -> {
                            String albumIndexStr = hit.source().toJson().toString();
                            AlbumInfoIndex hotAlbumInfoIndex = JSON.parseObject(albumIndexStr, AlbumInfoIndex.class);
                            return hotAlbumInfoIndex;
                        }).collect(Collectors.toList());
                //3.5 构建当前置顶三级分类热门专辑Map对象
                Map<String, Object> map = new HashMap<>();
                map.put("baseCategory3", baseCategory3Map.get(topCategory3Id));
                map.put("list", hotAlbumIndexList);
                return map;
            }).collect(Collectors.toList());
            return list;
        } catch (IOException e) {
            log.error("[搜索服务]置顶分类热门专辑异常：", e);
            throw new RuntimeException(e);
        }
    }


}
